/**
    MVP Tree data structure using graph database for I/O storage
    Copyright (C) 2014  David G. Starkweather

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    David Starkweather starkd88@gmail.com
**/

package org.phash.mvptree;

import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.index.IndexManager;
import org.neo4j.graphdb.index.Index;

/**
 * Class for representing a basic unit of data in 
 * MVPTree within a graph database.  Must be subclassed
 * to represent and store that data.
 **/
public abstract class Datapoint {

    protected final String ACTIVE = "ACTIVEPROPERTY";
    protected final String ID     = "IDPROPERTY";
    protected final String DATA   = "DATAPROPERTY";
    protected final String PATH   = "PATHPROPERTY";
    protected final Node node;

    static private int numOps = 0;

    /*
     * Datapoint constructor
     *
     * @param node neo4j Node with which to construct datapoint
     *
     **/
    public Datapoint(Node node){
	if (node == null)
	    throw new BadNodeException("null");

	this.node = node;
	setActive(true);
    }


    /* 
     * Datapoint copy constructor 
     *
     * @param point Datapoint to create a Datapoint
     *
     */
    public Datapoint(Datapoint point){
	this.node = point.getNode();
	setActive(true);

    }

    /** reset static variable, numOps to zero
     **/
    static public void resetNumOps(){

	Datapoint.numOps = 0;

    }

    /** increment static variable, numOps by one.
     **/
    static public void incrementNumOps(){
	
	Datapoint.numOps++;

    }

    /** return static variable numOps
     *  @return int
     **/
    static public int getNumOps(){

	return Datapoint.numOps;

    }

    /**
     * Retrieve underlying datapoint node
     *
     * @return Node underlying datapoint
     **/
    protected Node getNode(){
	
	return node;

    }

    /**
     * Set Active property of node
     *
     * @param active boolean value to set Datapoint
     *
     **/
    public void setActive(boolean active) {
	Node node = getNode();
	Transaction tx = node.getGraphDatabase().beginTx();
	try {
	    node.setProperty(ACTIVE, active);
	    tx.success();
	} finally {
	    tx.finish();
	}
    }

    /**
     * Get Active status of Datapoint 
     *
     * @return boolean value of active state of Datapoint
     **/
    public boolean isActive() {
	Node node = getNode();
	boolean flag = false;
	Transaction tx = node.getGraphDatabase().beginTx();
	try {
	    flag = (boolean)node.getProperty(ACTIVE, 0);
	    tx.success();
	} finally {
	    tx.finish();
	}
	return flag;
    }

    /**
     * Set Id string of Datapoint node
     *
     * @param id String with which to set the Datapoint 
     *
     **/
    public void setId(String id) {
	Node node = getNode();
	Transaction tx = node.getGraphDatabase().beginTx();
	try {
	    node.setProperty(ID, id);
	    tx.success();
	} finally {
	    tx.finish();
	}
    }

    /**
     * @return String value for id of Datapoint
     *
     **/
    public String getId() {
	Node node = getNode();
	String result = null;
	Transaction tx = node.getGraphDatabase().beginTx();
	try {
	    result = (String)node.getProperty(ID, 0);
	    tx.success();
	} finally {
	    tx.finish();
	}
	return result;
    }


    protected void setPathDistance(int n, float d){
	StringBuilder str = new StringBuilder(PATH);
	str.append(n);
	Node node = getNode();
	Transaction tx = node.getGraphDatabase().beginTx();
	try {
	    node.setProperty(str.toString(), d);
	    tx.success();
	} finally {
	    tx.finish();
	}
	
    }

    protected float getPathDistance(int n){
	StringBuilder str = new StringBuilder(PATH);
	str.append(n);
	Node node = getNode();
	Transaction tx = node.getGraphDatabase().beginTx();
	Float value = 0.0f;
	try {
	    value = (float)node.getProperty(str.toString(), -1.0f);
	    tx.success();
	} finally {
	    tx.finish();
	}
	return value;
    }

    /** Delete datapoint
     *  Delete the relationships connected to underlying node and the underlying node itself.
     *  Also remove the underlying node from the node index.
     **/
    public void delete() {
	/* delete datapoint node from graphdb and remove from index */
	Node node = getNode();
	Iterable<Relationship> rels = node.getRelationships();
	Index<Node> nodeIndex = node.getGraphDatabase().index().forNodes(NodeFactory.DATAPOINTINDEX);
	Transaction tx = node.getGraphDatabase().beginTx();
	try {
	    for (Relationship rel : rels){
		rel.delete();
	    }
	    nodeIndex.remove(getNode());
	    node.delete();
	    tx.success();
	} finally { 
	    tx.finish();
	}
    }

    /** detect if Datapoint is same as another Datapoint.
     * @param pnt
     **/
    public boolean isSameDatapoint(Datapoint pnt){

	if (getNode().getId() == pnt.getNode().getId()){
	    return true;
	}
	return false;
    }

    abstract float distance(Datapoint point);

}